'''
	The model module defines the model. The model is contains a collections
	of geoms. These collection of geoms share certain properties like their
	color. For convience a number of accesors are defined to make life in the 
	higher layers of the system more bearable.
	
	@author: J.H. Donker, J.Teeuw, S.G. Epskamp
	@organization: Twente University
'''

import wx
import vtk

from HeaDSETCube import *
from HeaDSETSphere import *
from HeaDSETCone import *
from HeaDSETCylinder import *

from xml.dom.minidom import *
from util import XmlUtil
from wx.lib.pubsub import Publisher

class HeaDSETModel:
	'''
		The HeaDSETModel is a wrapper for the collections of geoms. It handles
		these list genericly. And provides hooks for the VTK components for them
		to maintain state. The model has two modes ModelMode and NonModelMode. 
		In Model mode the system will not allow mutations to the tumor and safe
		path geoms.
	'''
	TUMOR = "Tumor"
	PATH  = "Safe path"
	PATH_SAVE = "Safe_path"
	
	def __init__(self):
		'''
			The constructor for the class initialises the standard lists and
			individual geoms. It also defines te standard colors for the different
			geom collections
		'''	
		# init model
		self.__dicom_model = None # NOTE: singular!
		self.__path_geoms = []
		self.__tumor_geoms = []
		self.__mode = True 
		
		self.__instrument = None
		
		self.__pathColor = (0,1,0)
		self.__tumorColor = (1,0,0)
		self.__instrumentColor = (0,0,1)
		
		self.__recordings = []
		
	def __addGeom(self, list, geom):
		'''
			Private method to add a geom to a collection and to notify
			the view that a geom has been added. 
			
			@param list: The internal collection that will be modified.
			@param geom: The geom that will be added to the specified list.
		'''
		##print "adding geom"
		# add to list
		list.append(geom)
		
		# add to view
		#actor = geom.GetActor()
		Publisher.sendMessage("GeomAdded", geom)
	
	
	def __removeGeom(self, list, geom):
		'''
			Private method to remove geom from a collection. The function
			also notifies the view of the removal of the geom.
			
			@param list: The internal collection that will be modified.
			@param geom: The geom that will be removed from the specified list.
		'''
		try:
			# remove from list
			list.remove(geom)
			
			# remove from view
			#actor = geom.GetActor()
			Publisher.sendMessage("GeomRemoved", geom)
			
		except ValueError:
			###print 'removegeom error'  
			# catched silly 'not in list' exceptions
			pass
			
	
	def __changeColor(self, list, color):
		'''
			Private method that changes the color for an entire list at once. 
			Will once change the color of the currently active geoms in the
			view.
			
			@param list: The internal collection that will be modified. 
			@param color: The color used for this entire collection
		'''
		# color components
		r, g, b = color
		
		# change color for all geoms in list
		for i in range(0, len(list)):
			list[i].SetColor(r, g, b)



	def __changeVisible(self, list, visible):
		'''
			Private method that changes the visibility of the entire list at 
			once to the user provided visiblity status.
			
			@param list: The internal collection that will be modified. 
			@param visible: The color used for this entire collection
		'''
		# change visibility for all geoms in list
		for i in range(0, len(list)):
			list[i].SetVisibility(visible)		
	
	
	def ModelMode(self):
		'''
			Set the system in ModelMode. This means that the system allows 
			changes to tumor and safe path geoms by the view.
		'''
		self.__mode = True
	
	
	def IsModelMode(self):
		'''
			Tells if the system is in ModelMode. 
			
			@return: True if the moel is in ModelMode. False if the system is not
			        in ModelMode.
		'''
		return self.__mode
	
	
	def NonModelMode(self):
		'''
			Set the system in ModelMode. This means that the system does not 
			allow changes to tumor and safe path geoms by the view.
		'''	
		self.__mode = False
	
			
	def IsNonModelMode(self):
		'''
			Tells if the system is in NonModelMode
			
			@return: True if the moel is in NonModelMode. False if the system is 
			        not in NonModelMode.
		'''
		return not self.__mode

	

	def SetDicomModel(self, geom):
		'''
			Set the Dicom for the current session.
			
			@param geom: The geom which will be considered a Dicom model.
		'''
		
		# add geom to list and view
		if self.__dicom_model == None:
			self.__dicom_model = geom
			Publisher.sendMessage("DicomAdded", self.__dicom_model)
		else:
			# RSLV: do nothing? or override with new dicom model?
			pass
	

	def GetDicomModel(self):
		
		return self.__dicom_model
	
	
	def UnsetDicomModel(self):
		'''
			Removes the Dicom model.
		'''
		# remove from list and view
		if self.__dicom_model != None:
			Publisher.sendMessage("DicomRemoved", self.__dicom_model)
			self.__dicom_model = None
		
	
	def AddToPath(self, geom):
		'''
			Adds a geom to the path list.
			
			@param geom: The geom that will be added to the path.
		'''
		# set color of path geom
		r, g, b = self.__pathColor
		geom.SetColor(r, g, b)
		
		# add geom to list and view
		self.__addGeom(self.__path_geoms, geom)
	
	
	def RemoveFromPath(self, geom):
		'''
			Removes a geom from the path list.
			
			@param geom: The geom that will be removed from the path list.
		'''
		# remove geom from list and view
		self.__removeGeom(self.__path_geoms, geom)
		
	
	def AddToTumor(self, geom):
		'''
			Adds a geom to the tumor list.
			
			@param geom: The geom that will be added to the tumor list.
		'''
		#  set color of tumor geom
		r, g, b = self.__tumorColor
		geom.SetColor(r, g, b)		
		
		# add geom to list and view
		self.__addGeom(self.__tumor_geoms, geom)
		##print self.__tumor_geoms
	

	def RemoveFromTumor(self, geom):
		'''
			Removes a geom from the tumor list.
			
			@param geom: The geom that will be removed from the the tumor list.
		'''
		# remove geom from list and view
		self.__removeGeom(self.__tumor_geoms, geom)
		
	

	def GetMutables(self):
		'''
			Returns a list of geoms that the view is allowed to manipulate.
			
			@return: List of mutable geoms
		'''
		# create list of mutable geoms
		
		mutables = []
		if self.__mode:
			mutables.extend(self.__path_geoms)
			mutables.extend(self.__tumor_geoms)
		
		# return list of mutable geoms
		return mutables
	
	
	def GetInstrument(self):
		'''
			Gives back the instrument geom.
			
			@return: The geom that is designated the instrument
		'''
		return	self.__instrument
	
	
	def GetTumorGeom(self, index):
		'''
			Gives back a tumor geom by index.
			
			@param index: The index of the geom in the list.
			@return: The requested geom
		'''
		return self.__tumor_geoms[index]
		
	
	def GetPathGeom(self, index):
		'''
			Gives back a path geom by index.
			
			@param index: The index of the geom in the list.
			@return: The requested geom.
		'''
		return self.__path_geoms[index]
		
		
	def GetNumberOfTumorGeoms(self):
		'''
			Returns the number of geoms that are in the Tumor list.
			
			@return: The number of tumor geoms. 
		'''
		return len(self.__tumor_geoms)
		
		
	def GetNumberOfPathGeoms(self):
		'''
			Returns the number of geoms that are in the Path list.
			
			@return: The number of path geoms. 
		'''
		return len(self.__path_geoms)
	
	
	def GetGeomByID(self, ID):
		'''
			Gives back the geom by it's ID.
			
			@param ID: The ID of the geom
			@return: The geom with the ID if in the system, if the geom is not
			        the system it will return None
		'''
		
		geoms = []
		geoms.extend(self.__path_geoms)
		geoms.extend(self.__tumor_geoms)
		#geoms.extend(self.__dicom_model)
		if self.__dicom_model != None:
			geoms.append(self.__dicom_model)
		
		if self.__instrument != None:
			geoms.append(self.__instrument)

		result = None
		found = False
		i = 0
		while not found and i < len(geoms):
			if geoms[i].GetID() == ID:
				result = geoms[i]
				found = True 
			i = i + 1
		return result
	
	
	def RemoveGeom(self, geom):
		'''
			Removes a mutable geom.
			
			@param geom: The geom that will be removed.
		'''
		self.RemoveFromTumor(geom)
		self.RemoveFromPath(geom)

	
	def Clone(self, geom):
		'''
			Clones a geom
			
			@param geom: The geom that will be cloned
		'''
		#find group
		tumor = self.IsTumorGeom(geom)
		path = self.IsPathGeom(geom)	
		#deep copy geom
		copy = geom.Clone()
		#add to group
		
		if tumor:
			self.AddToTumor(copy)
		elif path:
			self.AddToPath(copy)
		
		# set properties of cloned object
		xpos, ypos, zpos = geom.GetPosition()
		copy.SetPosition(xpos, ypos, zpos)
		
		xrot, yrot, zrot = geom.GetOrientation()
		copy.SetOrientation(xrot, yrot, zrot)
		
		xscale, yscale, zscale = geom.GetScale()
		print "Scales:"
		print xscale, yscale, zscale
		copy.SetScale(xscale, yscale, zscale)
		
		visibility = geom.GetVisibility()
		copy.SetVisibility(visibility)
		
		# RSLV: opacity, color?
		
	
	def DisplayInstrument(self):
		'''
			Enables the instrument if not present, if the instrument is invisble
			it is made visible again
		'''
		if self.__instrument == None:
			self.__instrument = HeaDSETCone("Instrument")
			self.__instrument.SetInstrument(True) 
			#Publisher.sendMessage("GeomAdded", self.__instrument)
			Publisher.sendMessage("InstrumentAdded", self.__instrument)
			self.__instrument.SetOpacity(0.35)
			red, green, blue = self.__instrumentColor
			self.__instrument.SetColor(red, green, blue)			
		else:
			self.__instrument.SetVisibile()
						
	
	def SetPathColor(self, red, green, blue):
		'''
			Changes the default path color
			
			@param red: The new default red component
			@param green: The new default green component
			@param blue: The new default blue component
		'''
		self.__pathColor = (red, green, blue)
		self.__changeColor(self.__path_geoms, self.__pathColor)
		
		
	def SetTumorColor(self, red, green, blue):
		'''
			Changes the default Tumor color.
			
			@param red: The new default red component
			@param green: The new default green component
			@param blue: The new default blue component			
		'''
		self.__tumorColor = (red, green, blue)
		self.__changeColor(self.__tumor_geoms, self.__tumorColor)
	
		
	def SetInstrumentColor(self, red, green, blue):
		'''
			Changes the default Instrument color.
			
			@param red: The new default red component
			@param green: The new default green component
			@param blue: The new default blue component			
		'''		
		self.__instrument.SetColor(red, green, blue)

	
	def SetVisibleGroup(self, name, visible):
		'''
			Changes the visibility of an entire collection at once. This is
			used mainly in the operation view. The name argument is a string
			containing the name of the collection that should be changed.
			
			@param name: The name of the collection.
			@param visible: The setting.
		'''
		if name == "Dicom":
			###print "dicom change"
			#self.__changeVisible(self.__dicom_model, visible)
			if self.__dicom_model != None:
				self.__dicom_model.SetVisibility(visible) # NOTE: also triggers GeomUpdated (etc.)!!!!
				Publisher.sendMessage("DicomUpdated") # make change in visibility visibile :D
		elif name == "Path":
			self.__changeVisible(self.__path_geoms, visible)
		elif name == "Tumor":
			self.__changeVisible(self.__tumor_geoms, visible)
		elif name == "Instrument" and self.GetInstrument() != None:
			self.GetInstrument().SetVisibility(visible)


	def IsTumorGeom(self, geom):
		'''
			Checks if the given geom is a tumor geom.
			
			@param geom: The geom that will be checked.
			@return: True if it's a tumor geom false if not.
		'''
		return geom in self.__tumor_geoms

			
	def IsPathGeom(self, geom):
		'''
			Checks if the given geom is a path geom.
			
			@param geom: The geom that will be checked.
			@return: True if it's a path geom false if not.
		'''
		return geom in self.__path_geoms
		


	def Save(self):
		'''
			Save state to xml.
			Save the al the attributes of all Geoms this model contains
			to an XML file.
			
			@return: A string containing the XML
		'''
		#print 'Saving state to XML: ' + fullPath
		# create DOM document
		impl = xml.dom.minidom.getDOMImplementation()
		doc = impl.createDocument(None, "headset", None)

		# get root element
		root = doc.documentElement
		
		# save geoms in tumor, path
		for name, geoms in zip((self.PATH_SAVE, self.TUMOR),(self.__path_geoms, self.__tumor_geoms)):
			curNode = root.appendChild(doc.createElement(name))
			for geom in geoms:
				curNode.appendChild(geom.Save(doc))
		
		
		XML = doc.toprettyxml()
		doc.unlink() # When you are finished with a DOM, you should clean it up. This is necessary because some versions of Python do not support garbage collection of objects that refer to each other in a cycle.
		return XML
	
	def Load(self, XML):
		'''
			Load geoms from an xml string as created by self.Save
			
			@param XML: xml string created by self.Save
		'''
		doc = xml.dom.minidom.parseString(XML)
		root = doc.documentElement
		for subTreeName, addFunction in zip( (self.TUMOR, self.PATH_SAVE) , (self.AddToTumor, self.AddToPath) ):
			for elem1 in doc.getElementsByTagName(subTreeName):
				##print 'Loading geoms from subtree:', elem1.tagName
				for elem2 in elem1.childNodes:
					if elem2.nodeType == xml.dom.minidom.Node.ELEMENT_NODE: # ignore whitespace textnodes
						self._LoadGeom(elem2, addFunction)


	def _LoadGeom(self, elem, addFunction):
		'''
			Creates a geom from the attributes described by elem, and call the method addFunction(newGeom).
			
			@param elem: DOM-element describing a geom.
			@param addFunction: function accepting the geom as a single parameter.
		'''
		##print '  Loading', elem.tagName
		# map geom strings to class instances with default settings
		mapping = {
			'HeaDSETCube' :  HeaDSETCube("Cube"),
			'HeaDSETSphere' : HeaDSETSphere("Sphere"),
			'HeaDSETCylinder' : HeaDSETCylinder("Cylinder"),
			'HeaDSETCone' : HeaDSETCone("Cone")
			}
		geom = mapping[elem.tagName]
		
		# add geom
		addFunction(geom)

		xyz = ('x','y','z')
		rgb = ('r','g','b')
		name2function = {
			HeaDSETGeom.POSITION : (xyz, geom.SetPosition),
			HeaDSETGeom.ORIENTATION : (xyz, geom.SetOrientation),
			HeaDSETGeom.SCALE : (xyz, geom.SetScale),
			HeaDSETGeom.COLOR : (rgb, geom.SetColor),
			}
		
		for name, name_function in name2function.iteritems():
			for tmpElem in elem.getElementsByTagName(name):
				values = XmlUtil.readAttributes(tmpElem, name_function[0]) #read the attribute(s) from XML
				###print values, '-', name
				name_function[1](*values) # set the attribute(s) on the geom
		
		name2function = {
			HeaDSETGeom.OPACITY : geom.SetOpacity,
			HeaDSETGeom.LABEL : geom.SetLabel,
			}
		
		for name, function in name2function.iteritems():
			value = XmlUtil.readAttributes(elem, (name,)) # see comma behind name, tuple with 1 value
			###print value, '-', name
			function(*value)
		

	def Reset(self):
		'''
			Resets the model. All geoms are removed and ID's are reset to 1.
		'''
		self.UnsetDicomModel()
		for geom in self.__path_geoms[:]: # make a slice copy of the entire list, because it is modified
			self.RemoveFromPath(geom)
		for geom in self.__tumor_geoms[:]:
			self.RemoveFromTumor(geom)
		if self.__instrument != None:
			Publisher.sendMessage("InstrumentRemoved", self.__instrument)	
			self.__instrument = None
		self.__recordings = []
		HeaDSETGeom.GEOM_ID = 1
		
		
	def AddRecording(self, recording):
		'''
			Adds a recording to the model.
			
			@param recording: The recording to be stored.
		'''
		print "adding recording"
		self.__recordings.append(recording)
		Publisher.sendMessage("RecordingAdded", (recording, len(self.__recordings) -1))
		
	def GetRecording(self, index):
		'''
			Returns the recording belonging to a certain index. 
			
			@param index: The index of the requested recording.
		'''
		return self.__recordings[index]
		
	def GetNumberOfRecordings(self):
		'''
			Returns the numbger of recordings in the model.
			
			@return: The number of geoms. 
		'''
		return len(self.__recordings)
